package com.gagakuai.datasource.pooled;

import com.gagakuai.datasource.unpooled.UnpooledDataSource;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;

/*
 * @program: settement
 * @description: 连接池
 * @author: houhong
 * @create: 2022-11-08 22:22
 */
public class PoolDataSource implements DataSource {


    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(PoolConnection.class);
    /*
     * 连接状态
     */
    private PoolState poolState = new PoolState(this);

    /*
     * 未池化的资源
     */
    private UnpooledDataSource unpooledDataSource;

    /*===========可配置属性=============*/
    /*
     * 最大活跃数
     */
    protected int poolMaximumActiveConnections = 10;
    /*
     * 空闲数量
     */
    protected int poolMaximumIdleConnections = 5;
    /*
     * 在被强制返回之前,池中连接被检查的时间
     */
    protected int poolMaximumCheckoutTime = 20000;
    /*
     * 这是给连接池一个打印日志状态机会的低层次设置,还有重新 尝试获得连接, 这些情况下往往需要很长时间 为了避免连接池没有配置时静默失 败)。
     */
    protected int poolTimeToWait = 20000;
    /*
     * 发送到数据的侦测查询,用来验证连接是否正常工作,并且准备 接受请求。默认是“NO PING QUERY SET” ,这会引起许多数据库驱动连接由一 个错误信息而导致失败
     */
    protected String poolPingQuery = "NO PING QUERY SET";
    /*
     * 开启或禁用侦测查询
     */
    protected boolean poolPingEnabled = false;
    /*
     * 用来配置 poolPingQuery 多次时间被用一次
     */
    protected int poolPingConnectionsNotUsedFor = 0;

    /*
     * 连接类型
     */
    private int expectedConnectionTypeCode;


    public PoolDataSource() {
        unpooledDataSource = new UnpooledDataSource();
    }

    public PoolDataSource(String driver, String url, String username, String password) {
        unpooledDataSource = new UnpooledDataSource(driver, url, username, password);
        expectedConnectionTypeCode = assembleConnectionTypeCode(unpooledDataSource.getUrl(), unpooledDataSource.getUsername(), unpooledDataSource.getPassword());
    }

    public PoolDataSource(String driver, String url, Properties driverProperties) {
        unpooledDataSource = new UnpooledDataSource(driver, url, driverProperties);
        expectedConnectionTypeCode = assembleConnectionTypeCode(unpooledDataSource.getUrl(), unpooledDataSource.getUsername(), unpooledDataSource.getPassword());
    }

    public PoolDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
        unpooledDataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
        expectedConnectionTypeCode = assembleConnectionTypeCode(unpooledDataSource.getUrl(), unpooledDataSource.getUsername(), unpooledDataSource.getPassword());
    }

    public PoolDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
        unpooledDataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
        expectedConnectionTypeCode = assembleConnectionTypeCode(unpooledDataSource.getUrl(), unpooledDataSource.getUsername(), unpooledDataSource.getPassword());
    }


    /*
     * @Description //TODO  用来关闭连接池内的所有连接
     */
    public void forceCloseAll() {

        synchronized (poolState) {

            expectedConnectionTypeCode = assembleConnectionType(unpooledDataSource.getUrl(),
                    unpooledDataSource.getUsername(), unpooledDataSource.getPassword());

            //关闭活跃连接
            for (int i = poolState.getActiveList().size(); i > 0; i--) {

                PoolConnection conn = poolState.getActiveList().remove(i - 1);
                //连接无效化
                conn.invalidate();

                Connection realConnection = conn.getRealConnection();

                try {
                    if (!realConnection.getAutoCommit()) {
                        realConnection.rollback();
                    }
                    realConnection.close();
                } catch (Exception e) {
                    //// TODO: 2022/11/8
                }
            }

            //关闭空闲连接
            for (int i = poolState.getIdleList().size(); i > 0; i--) {

                PoolConnection idelConn = poolState.getIdleList().remove(i - 1);
                idelConn.invalidate();

                Connection realConnection = idelConn.getRealConnection();
                try {
                    if (!realConnection.getAutoCommit()) {
                        realConnection.rollback();
                    }
                    realConnection.close();
                } catch (Exception e) {
                    //// TODO: 2022/11/8
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("PooledDataSource forcefully closed/removed all connections.");
            }
        }


    }

    /*
     * @param password
     * @param username
     * @Description //TODO
     * 从连接池中获取一个连接
     */
    public PoolConnection popConnection(String username, String password) throws SQLException {

        PoolConnection conn = null;

        boolean countedWait = false;

        int localBadConnectionCount = 0;

        long starteTime = System.currentTimeMillis();
        /*
         *  1: 如果有空闲连接。从空闲中拿一个
         *  2: 如果没有空闲连接
         *       2.1: 活跃连接少于 一个值,new 一个连接，
         *       2.2  如果activeConnections已经很多了，那不能再new了
         *              2.2.1: 取得activeConnections列表的第一个（最老的）
         *              2.2.2: 如果checkout时间过长，则这个connection标记为overdue（过期）
         *                       //删掉最老的连接，然后再new一个新连接
         *                    //如果checkout时间不够长，等待
         */
        while (conn == null) {

            synchronized (poolState) {

                if (!poolState.getIdleList().isEmpty()) {

                    conn = poolState.getIdleList().remove(0);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                    }
                } else {
                    //未到最大活跃数目
                    if (poolState.getActiveList().size() < poolMaximumActiveConnections) {
                        conn = new PoolConnection(unpooledDataSource.getConnection(), this);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Created connection " + conn.getRealHashCode() + ".");
                        }
                    } else {

                        //取得activeConnections列表的第一个（最老的）
                        PoolConnection oldestActiveConnction = poolState.getActiveList().get(0);
                        long oldestActiveConnctionCheckoutTime = oldestActiveConnction.getCheckoutTime();
                        //等待活跃连接的释放时间
                        if (oldestActiveConnctionCheckoutTime > poolMaximumCheckoutTime) {

                            poolState.claimedOverdueConnectionCount++;
                            poolState.accumulatedCheckoutTimeOfOverdueConnections += oldestActiveConnctionCheckoutTime;
                            //todo 删除最老的一个连接
                            poolState.getActiveList().remove(oldestActiveConnction);
                            conn = new PoolConnection(oldestActiveConnction.getRealConnection(), this);

                            if (!oldestActiveConnction.getRealConnection().getAutoCommit()) {
                                oldestActiveConnction.getRealConnection().rollback();
                            }

                            if (logger.isDebugEnabled()) {
                                logger.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                            }
                        } else {
                            //设定等待最长的释放时间 < 继续等最老活跃连接的释放
                            try {
                                if (!countedWait) {
                                    //统计信息：等待+1
                                    poolState.hadToWaitCount++;
                                    countedWait = true;
                                }
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                                }
                                long wt = System.currentTimeMillis();
                                poolState.wait(poolTimeToWait);

                                poolState.accumulatedWaitTime += wt;
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }

                if (conn != null) {
                    //如果已经拿到connection，则返回
                    if (conn.isValid()) {

                        if (!conn.getRealConnection().getAutoCommit()) {
                            conn.getRealConnection().rollback();
                        }
                        conn.setConnectionTypeCode(assembleConnectionTypeCode(unpooledDataSource.getUrl(), username, password));
                        //记录checkout时间
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        poolState.getActiveList().add(conn);
                        poolState.requestCount++;
                        poolState.accumulatedRequestTime += System.currentTimeMillis() - starteTime;
                    } else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                        }
                        //如果没拿到，统计信息：坏连接+1
                        poolState.badConnectionCount++;
                        localBadConnectionCount++;
                        conn = null;
                        if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
                            //如果好几次都拿不到，就放弃了，抛出异常
                            if (logger.isDebugEnabled()) {
                                logger.debug("PooledDataSource: Could not get a good connection to the database.");
                            }
                            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                        }
                    }
                }
            }
        }

        return conn;
    }

    private int assembleConnectionTypeCode(String url, String username, String password) {
        return ("" + url + username + password).hashCode();
    }


    /*
     * @param connection
     * @Description //TODO 将连接放回pool池
     * @Date 12:14 上午 2022/11/9
     */
    public void pushConnection(PoolConnection connection) throws SQLException {


        /*
         *   1：当执行关闭的时候，需要将该连接放回连接池
         *     1.1,当空闲连接未达到最大空闲数当时候，就放回到空闲列表
         *     1.2:当达到最大空闲列表数量到时候， 需要关闭真正和数据库建立的连接
         */
        synchronized (poolState) {

            poolState.getActiveList().remove(connection);

            //当前连接有效
            if (connection.isValid()) {

                if (poolState.getIdleList().size() < poolMaximumIdleConnections
                        && connection.getConnectionTypeCode() == expectedConnectionTypeCode) {

                    poolState.accumulatedCheckoutTime += connection.getCheckoutTime();

                    if (!connection.getRealConnection().getAutoCommit()) {
                        connection.getRealConnection().rollback();
                    }

                    PoolConnection newPoolConnection = new PoolConnection(connection.getRealConnection(), this);
                    poolState.getIdleList().add(newPoolConnection);
                    newPoolConnection.setCreatedTimestamp(connection.getCreatedTimestamp());
                    newPoolConnection.setLastUsedTimestamp(connection.getLastUsedTimestamp());
                    //让PoolConecton连接无效化
                    connection.invalidate();
                    if (logger.isDebugEnabled()) {
                        logger.debug("Returned connection " + newPoolConnection.getRealHashCode() + " to pool.");
                    }
                    //通知其他线程获取连接
                    poolState.notifyAll();

                } else {

                    //空闲连接已经够了
                    poolState.accumulatedCheckoutTime += connection.getCheckoutTime();
                    //直接关闭
                    Connection realConn = connection.getRealConnection();
                    if (!realConn.getAutoCommit()) {
                        realConn.rollback();
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Closed connection " + connection.getRealHashCode() + ".");
                    }
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("A bad connection (" + connection.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
                }
                poolState.badConnectionCount++;
            }
        }


    }

    /*
     * @param conn
     * @Description //TODO 心跳检测
     * @Date 12:14 上午 2022/11/9
     */
    protected boolean pingConnection(PoolConnection conn) {

        return false;
    }

    private int assembleConnectionType(String url, String username, String password) {
        return ("" + url + username + password).hashCode();
    }


    @Override
    public Connection getConnection() throws SQLException {
        //覆盖了DataSource.getConnection方法，每次都是pop一个Connection，即从池中取出一个来
        return popConnection(unpooledDataSource.getUsername(), unpooledDataSource.getPassword())
                .getProxyConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {

        return popConnection(username, password).getProxyConnection();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new SQLException(getClass().getName() + " is not a wrapper.");
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return DriverManager.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        DriverManager.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        DriverManager.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return DriverManager.getLoginTimeout();
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }
}